package co.dianjiu.nio.client;

import co.dianjiu.nio.util.InputUtils;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;

/**
 * @author DianJiu
 * @website https://dianjiu.co
 * @email dianjiu@dianjiu.cc
 * @date 2021/8/2 17:20
 * @desc TODO
 */
public class NIOClient {
    private static Selector selector;
    private static boolean flag = true;

    public static void main(String[] args) throws Exception{
        init();
        request();
    }

    public static void init() throws IOException{
        //客户端也用nio，事实上并不是非要用selector形式的nio,一单通过网络连接，和如何实现就无关了，后面贴了个传统socket客户端的代码
        selector = Selector.open();
        //打开客户端连接，相对服务端只是少了个Server前缀和传统Socket一样
        SocketChannel sc = SocketChannel.open();
        sc.configureBlocking(false);
        //绑定请求地址端口
        sc.connect(new InetSocketAddress("127.0.0.1", 10010));
        //注册connect事件，相对于服务端注册的OP_ACCEPT
        sc.register(selector, SelectionKey.OP_CONNECT);
    }

    public static void request() throws IOException{
        out:while(flag){
            selector.select();
            Set<SelectionKey> selectedKeys = selector.selectedKeys();
            Iterator<SelectionKey> it = selectedKeys.iterator();
            while (it.hasNext()){
                SelectionKey key = it.next();
                // 由于select操作只管对selectedKeys进行添加，所以key处理后我们需要从里面把key去掉
                it.remove();
                //如果selector检测到连接事件，第一次进来肯定是连接事件，因为上面只注册了连接事件
                if(key.isConnectable()){
                    SocketChannel channel = (SocketChannel) key.channel();
                    //如果完成了连接
                    if(channel.finishConnect()){
                        channel.configureBlocking(false);
                        //注册写事件,因为模拟http的形式，所以客户端是先写，再读，
                        //channel.register(selector, SelectionKey.OP_WRITE);
                        //即先请求在得到响应数据，如果是想保持长连接则写成如下形式
                        channel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
                    }
                }
                //在第二次进来写数据后注册了读事件，则第三次进来就会读到 读就绪事件
                if(key.isReadable()){
                    //读取数据
                    String read = read(key);
                    //读完数据注册写事件，再次请求服务端
                    key.interestOps(SelectionKey.OP_WRITE);
                    // 改变自身关注事件，此处为清理所有监听事件，模拟http中相应完关闭此连接
                    if("886".equalsIgnoreCase(read)){
                        //清理事件监听
                        key.interestOps(0);
                        //关闭连接
                        key.channel().close();
                        //跳出循环，此处不建议这么
                        break out;
                    }
                }
                //连接完成注册好写事件，第二次进来则检测到的就是写事件
                if(key.isWritable()){
                    //写出数据
                    write(key);
                    //写完数据注册读事件，用于接受数据
                    key.interestOps(SelectionKey.OP_READ);
                }
            }
        }
    }

    private static void write(SelectionKey key) throws IOException {
        //获取控制台输入流
        Scanner scan = new Scanner(System.in);
        scan.useDelimiter("\n");
        String inputData = InputUtils.getString("请输入要发送的内容：").trim();
        //发送请求
        SocketChannel channel = (SocketChannel) key.channel();
        channel.write(ByteBuffer.wrap(inputData.getBytes()));
    }

    private static String read(SelectionKey key) throws IOException {
        SocketChannel channel = (SocketChannel) key.channel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int count = channel.read(buffer);
        byte[] bytes = new byte[count];
        buffer.flip();
        buffer.get(bytes);
        System.out.println("收到服务端回复消息：\n" + new String(bytes));
        return new String(bytes);
    }

}
